package com.glodon.paas.account.service.impl;

import static com.glodon.paas.account.api.bean.AccountConstant.*;

import java.sql.Timestamp;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.validator.routines.EmailValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;

import com.glodon.paas.account.api.bean.Member;
import com.glodon.paas.account.api.bean.Org;
import com.glodon.paas.account.api.bean.User;
import com.glodon.paas.account.api.exception.DuplicateInviteException;
import com.glodon.paas.account.api.exception.DuplicateSendMailException;
import com.glodon.paas.account.dao.mapper.OrgMapper;
import com.glodon.paas.account.service.OrgService;
import com.glodon.paas.account.service.UserService;
import com.glodon.paas.exception.PaasException;
import com.glodon.paas.exception.PaasServerException;
import com.glodon.paas.mail.SpringMailMessage;
import com.glodon.paas.mail.SpringMailSender;
import com.glodon.paas.util.UUIDUtil;

@Service
public class OrgServiceImpl implements OrgService {

    private static final int BLOCK_INVITE_EMAIL_TIME = 5 * 60 * 1000;
    @Autowired
    private OrgMapper orgMapper;
    @Autowired
    private UserService userService;
    @Autowired
    private SpringMailSender springMailSender;
    @Value("${account.server.url}")
    private String accountServerUrl;

    @Override
    public void addOrg(Org org) throws PaasException {
        Org duplicate = orgMapper.getOrgByOwnerParentName(org);
        if (duplicate != null)
            throw new PaasException("duplicate org");
        org.setId(UUIDUtil.getUUIDStr());
        if (org.getParentId() != null) {
            Org parentOrg = getOrg(org.getParentId());
            if (parentOrg == null)
                throw new PaasException("parent org is null");
            org.setRootId(parentOrg.getRootId());
        } else {
            org.setRootId(org.getId());
        }
        orgMapper.insert(org);
    }

    @Override
    public List<Org> getOrgs(String ownerId) {
        List<Org> allOrg = orgMapper.selectByOwner(ownerId);
        Map<String, Org> tmp = new HashMap<String, Org>();
        for (Org org : allOrg) {
            tmp.put(org.getId(), org);
        }
        List<Org> result = new ArrayList<Org>();
        for (Org org : allOrg) {
            if (org.getParentId() == null) {
                result.add(org);
            } else {
                Org parent = tmp.get(org.getParentId());
                org.setParentId(parent.getId());
                parent.addSubOrg(org);
            }
        }
        return result;
    }

    @Override
    public List<String> addMemeber(String orgId, String... keys) throws PaasServerException {
        try {
            Org org = getOrg(orgId);
            if (org == null)
                throw new PaasServerException(50002, "org is not exists", null);
            List<String> inviteCodes = new ArrayList<String>();
            for (String key : keys) {
                String inviteCode = createInviteCode();
                Member member = new Member();
                member.setStatus(Member.PENDING);
                member.setOrgId(orgId);
                member.setRootOrgId(org.getRootId());
                User user;
                if (EmailValidator.getInstance().isValid(key)) {
                    member.setEmail(key);
                    user = userService.getUserByAccountName(key, ACCOUNT_NAME_EMAIL, USER_STATUS_ACTIVE);
                } else {
                    member.setMobile(key);
                    user = userService.getUserByAccountName(key, ACCOUNT_NAME_MOBILE, USER_STATUS_ACTIVE);
                }
                if (user != null)
                    member.setUserId(user.getId());
                else
                    member.setStatus(Member.NO_REG);
                member.setInviteCode(inviteCode);
                inviteCodes.add(inviteCode);
                orgMapper.insertMember(member);
                sendInviteMail(org, member);
            }
            return inviteCodes;
        } catch (DuplicateKeyException e) {
            throw new DuplicateInviteException("duplicate invite", e);
        }
    }

    @Override
    public void sendInviteMail(Member member) throws PaasServerException {
        Org org = getOrg(member.getOrgId());
        sendInviteMail(org, member);
    }

    private void sendInviteMail(Org org, Member member) throws PaasServerException {
        Timestamp updateTimeDate = member.getUpdateTime();
        Date now = new Date();
        long nowTime = now.getTime();
        if (updateTimeDate != null) {
            long updateTime = updateTimeDate.getTime();
            if (nowTime - updateTime < BLOCK_INVITE_EMAIL_TIME)
                throw new DuplicateSendMailException("invite send mail error");
        }
        String email = member.getEmail();
        if (email != null) {
            Org root = getRootOrg(org);
            String inviterName = getInviterName(org);
            String inviteCodeBase64 = getInviteCodeBase64(member);
            if (member.getUserId() != null) {
                sendMailInvoke(email, inviterName, root.getName(), org.getName(), inviteCodeBase64);
            } else {
                sendMailReg(email, inviterName, root.getName(), org.getName(), inviteCodeBase64);
            }
            member.setUpdateTime(new Timestamp(nowTime));
            orgMapper.updateOrgMemberUpdateTime(member);
        }
    }

    private String getInviteCodeBase64(Member member) {
        String inviteCode = member.getInviteCode();
        String inviteCodeBase64 = Base64.encodeBase64String(inviteCode.getBytes());
        return inviteCodeBase64;
    }

    private String getInviterName(Org org) {
        String inviterId = org.getOwner();
        User inviter = userService.getUserById(inviterId);
        String inviterName = inviter.getDisplayName();
        return inviterName;
    }

    private Org getRootOrg(Org org) {
        String rootId = org.getRootId();
        Org root;
        if (rootId == org.getId())
            root = org;
        else
            root = getOrg(rootId);
        return root;
    }

    private void sendMailReg(String email, String inviter, String orgName, String instName, String inviteCode) {
        sendInvite(email, inviter, orgName, instName, inviteCode, "invite register document and join to organization",
                "org_invite_reg.vm");
    }

    private void sendMailInvoke(String email, String inviter, String orgName, String instName, String inviteCode) {
        sendInvite(email, inviter, orgName, instName, inviteCode, "invite add to organization", "org_invite.vm");
    }

    private void sendInvite(String email, String inviter, String orgName, String instName, String inviteCode,
            String subject, String template) {
        String templateLocation = "mailTemplate/" + template;
        HashMap<String, Object> param = new HashMap<String, Object>();
        param.put("email", email);
        param.put("inviter", inviter);
        param.put("orgName", orgName);
        param.put("instName", instName);
        param.put("inviteCode", inviteCode);
        param.put("baseUrl", accountServerUrl);
        springMailSender.sendAsync(new SpringMailMessage(new String[] { email }, subject, templateLocation, param));
    }

    private String createInviteCode() {
        int random = (int) (Math.random() * 1000000);
        DecimalFormat f = new DecimalFormat("000000");
        return f.format(random);
    }

    @Override
    public Org getOrg(String id) {
        return orgMapper.selectById(id);
    }

    public void setOrgMapper(OrgMapper orgMapper) {
        this.orgMapper = orgMapper;
    }

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void setSpringMailSender(SpringMailSender springMailSender) {
        this.springMailSender = springMailSender;
    }

    public void setAccountServerUrl(String accountServerUrl) {
        this.accountServerUrl = accountServerUrl;
    }

    @Override
    public void updateOrg(Org org) {
        Org old = orgMapper.selectById(org.getId());
        if (!old.sibling(org) || old.getSeq() != org.getSeq()) {
            List<Org> orgs = orgMapper.selectByParentId(org.getParentId());
            orgs.remove(org);
            for (int i = 0; i < orgs.size(); i++) {
                Org o = orgs.get(i);
                int seq = i + 1;
                if (seq >= org.getSeq()) {
                    seq++;
                }
                o.setSeq(seq);
                orgMapper.updateById(o);
            }
        }
        orgMapper.updateById(org);
    }

    @Override
    public void removeMember(String orgId, String key) {
        if (EmailValidator.getInstance().isValid(key)) {
            orgMapper.deleteMemeberByEmail(orgId, key);
        } else {
            orgMapper.deleteMemeberByMobile(orgId, key);
        }
    }

    @Override
    public boolean isMember(String userId, String orgId) {
        Org org = getOrg(orgId);
        if (userId.equals(org.getOwner()))
            return true;
        List<Member> members = org.getMembers();
        for (Member member : members) {
            if (userId.equals(member.getUserId()))
                return true;
        }
        List<Org> subOrgs = getSubOrgs(org.getId());
        if (subOrgs.isEmpty())
            return false;
        for (Org subOrg : subOrgs) {
            if (isMember(userId, subOrg.getId()))
                return true;
        }
        return false;
    }

    @Override
    public List<Org> getSubOrgs(String id) {
        return orgMapper.getSubOrgsWithMember(id);
    }

    @Override
    public void deleteOrg(String id) throws PaasException {
        Org org = getOrg(id);
        if (org.getSubOrgs().size() > 0 || org.getMembers().size() > 0)
            throw new PaasException("sub orgs size: " + org.getSubOrgs().size() + ", member size: "
                    + org.getMembers().size());
        orgMapper.deleteById(id);
    }

    @Override
    public void deleteOrg(String id, boolean force) {
        orgMapper.deleteById(id);
    }

    @Override
    public void accept(String userId, String email, String inviteCode) {
        int status = Member.ACCEPT;
        Member member = orgMapper.getNoRegOrgMember(email, inviteCode);
        if (member != null) {
            orgMapper.updateMemberUserId(userId, email);
        }
        orgMapper.updateOrgMember(status, userId, inviteCode);
    }

    @Override
    public Member getMember(String orgId, String email) {
        return orgMapper.getOrgMemberByEmail(orgId, email);
    }

    @Override
    public List<Org> getOrgAndMembers(String id) {
        // TODO Auto-generated method stub
        return orgMapper.getSubOrgsWithMembers(id);
    }
}
